/**

 * HTML2Markdown - An HTML to Markdown converter.

 * 

 * This implementation uses HTML DOM parsing for conversion. Parsing code was

 * abstracted out in a parsing function which should be easy to remove in favor

 * of other parsing libraries.

 * 

 * Converted MarkDown was tested with ShowDown library for HTML rendering. And

 * it tries to create MarkDown that does not confuse ShowDown when certain

 * combination of HTML tags come together.

 * 

 * @author Himanshu Gilani

 * @author Kates Gasis (original author)

 * 

 */



if (typeof require != "undefined") {

	var htmlparser = require("./htmldomparser");

	var HTMLParser = htmlparser.HTMLParser;

}



/**

 * HTML2Markdown

 * @param html - html string to convert

 * @return converted markdown text

 */

function HTML2Markdown(html, opts) {

	var logging = false;

	var nodeList = [];

	var listTagStack = [];

	var linkAttrStack = [];

	var blockquoteStack = [];

	var preStack = [];

	

	var links = [];

	

	opts = opts || {};

	var inlineStyle = opts['inlineStyle'] || false;



	var markdownTags = {

		"hr": "- - -\n\n",

		"br": "  \n",

		"title": "# ",

		"h1": "# ",

		"h2": "## ",

		"h3": "### ",

		"h4": "#### ",

		"h5": "##### ",

		"h6": "###### ",

		"b": "**",

		"strong": "**",

		"i": "_",

		"em": "_",

		"dfn": "_",

		"var": "_",	

		"cite": "_",

		"span": " ",

		"ul": "* ",

		"ol": "1. ",

		"dl": "- ",

		"blockquote": "> "

	};



	function getListMarkdownTag() {

		var listItem = "";		

		if(listTagStack) {

			for ( var i = 0; i < listTagStack.length - 1; i++) {

				listItem += "  ";

			}			

		}

		listItem += peek(listTagStack);		

		return listItem;

	}

	

	function convertAttrs(attrs) {

		var attributes = {};

		for(var k in attrs) {

			var attr = attrs[k];

			attributes[attr.name] = attr;

		}

		return attributes;

	}



	function peek(list) {

		if(list && list.length > 0) {

			return list.slice(-1)[0];	

		} 

		return "";		

	}



	function peekTillNotEmpty(list) {

		if(!list) {

			return "";

		}

				

		for(var i = list.length - 1; i>=0; i-- ){

			if(list[i] != "") {

				return list[i];

			} 		

		}		

		return "";

	}

	

	function removeIfEmptyTag(start) {

		var cleaned = false;

		if(start == peekTillNotEmpty(nodeList)) {

			while(peek(nodeList) != start) {

				nodeList.pop();

			}

			nodeList.pop();

			cleaned = true;

		} 

		return cleaned;

	}

	

	function sliceText(start) {

		var text = [];

		while(nodeList.length > 0 && peek(nodeList) != start) {

			var t = nodeList.pop();

			text.unshift(t);

		}

		return text.join("");

	}

	

	function block(isEndBlock) {

		var lastItem = nodeList.pop();

		if (!lastItem) {

			return;

		}

		

		if(!isEndBlock) {

			var block;

			if(/\s*\n\n\s*$/.test(lastItem)) {

				lastItem = lastItem.replace(/\s*\n\n\s*$/, "\n\n");

				block = "";

			} else if(/\s*\n\s*$/.test(lastItem)) {

				lastItem = lastItem.replace(/\s*\n\s*$/, "\n");

				block = "\n";

			} else if(/\s+$/.test(lastItem)) {				

				block = "\n\n";

			} else {

				block = "\n\n";

			} 

			

			nodeList.push(lastItem);

			nodeList.push(block);	

		} else {

			nodeList.push(lastItem);

			if(!lastItem.endsWith("\n")) {

				nodeList.push("\n\n");

			}

		}

 	}

	

	function listBlock() {

		if(nodeList.length > 0) {

			var li = peek(nodeList);



			if(!li.endsWith("\n")) {

				nodeList.push("\n");

			} 

		} else {

			nodeList.push("\n");

		}

	}

	

	try {

		var dom;

		if(html) {

		var e = document.createElement('div');

			e.innerHTML = html;

			dom = e;

		} else {

			dom = window.document.body;

		}



		HTMLParser(dom,{

			start: function(tag, attrs, unary) {

				tag = tag.toLowerCase();

				if(logging) {

					console.log("start: "+ tag);

				}

				

				if(unary && (tag != "br" && tag != "hr" && tag != "img")) {

					return;

				}

				

				switch (tag) {

				case "br":

					nodeList.push(markdownTags[tag]);

					break;

				case "hr":

					block();

					nodeList.push(markdownTags[tag]);

					break;

				case "title":	

				case "h1":

				case "h2":

				case "h3":

				case "h4":

				case "h5":

				case "h6":

					block();

					nodeList.push(markdownTags[tag]);

					break;

				case "b":

				case "strong":

				case "i":

				case "em":

				case "dfn": 

				case "var": 	

				case "cite":

					nodeList.push(markdownTags[tag]);

					break;

				case "span":

					if(! /\s+$/.test(peek(nodeList))) {

						nodeList.push(markdownTags[tag]);	

					}

					break;

				case "p":

				case "div":				

				case "td":

					block();

					break;

				case "ul":

				case "ol":

				case "dl":	

					listTagStack.push(markdownTags[tag]);

					// lists are block elements

					if(listTagStack.length > 1) {

						listBlock();

					} else {						

						block();

					}										

					break;

				case "li":

				case "dt":

					var li = getListMarkdownTag();

					nodeList.push(li);

					break;

				case "a":					

					var attribs = convertAttrs(attrs);

					linkAttrStack.push(attribs);

					nodeList.push("[");

					break;

				case "img":

					var attribs = convertAttrs(attrs);

					var alt, title, url; 

					

					attribs["src"] ? url = getNormalizedUrl(attribs["src"].value) : url = "";

					if(!url) {

						break;

					}

					

					attribs['alt'] ? alt = attribs['alt'].value.trim() : alt = "";

					attribs['title'] ? title = attribs['title'].value.trim() : title = "";

										

					// if parent of image tag is nested in anchor tag use inline style

					if(!inlineStyle && !peekTillNotEmpty(nodeList).startsWith("[")) {						

						var l = links.indexOf(url);

						if(l == -1) {

							links.push(url);

							l=links.length-1;							 

						}		

						

						block();

						nodeList.push("![");

						if(alt!= "") {

							nodeList.push(alt);

						} else if (title != null) {

							nodeList.push(title);

						} 

						

						nodeList.push("][" + l + "]");

						block();

					} else {

						//if image is not a link image then treat images as block elements

						if(!peekTillNotEmpty(nodeList).startsWith("[")) {

							block();	

						}

						

						nodeList.push("![" + alt + "](" + url + (title ? " \"" + title + "\"" : "") + ")");

						

						if(!peekTillNotEmpty(nodeList).startsWith("[")) {

							block(true);	

						}

					}

					break;	

				case "blockquote":

					block();

					blockquoteStack.push(markdownTags[tag]);

					nodeList.push(blockquoteStack.join(""));

					break;

				case "pre":

				case "code":

					block();

					preStack.push(true);

					break;

				}				

			},

			chars: function(text) {			

				if(preStack.length > 0) {

					text = "    " + text.replace(/\n/g,"\n    ");

				} else if(text.trim() != "") {

					text = text.replace(/\s+/g, " ");

					

					var prevText = peekTillNotEmpty(nodeList);

					if(/\s+$/.test(prevText)) {

						text = text.replace(/^\s+/g, "");

					}	

				} else {

					nodeList.push("");

					return;

				}



				if(logging) {

					console.log("text: "+ text);

				}

				

				nodeList.push(text);

			},

			end: function(tag) {

				tag = tag.toLowerCase();

				if(logging) {

					console.log("end: "+ tag);

				}

				switch (tag) {				

				case "title":	

				case "h1":

				case "h2":

				case "h3":

				case "h4":

				case "h5":

				case "h6":

					if(!removeIfEmptyTag(markdownTags[tag])) {

						block(true);

					}

					break;

				case "p":

				case "div":

				case "td":

					while(nodeList.length > 0 && peek(nodeList).trim() == "") {

						nodeList.pop();

					}

					block(true);

					break;

				case "b":

				case "strong":

				case "i":

				case "em":

				case "dfn": 

				case "var": 	

				case "cite":

					if(!removeIfEmptyTag(markdownTags[tag])) {						

						nodeList.push(sliceText(markdownTags[tag]).trim());

						nodeList.push(markdownTags[tag]);

					}

					break;

				case "a":

					var text = sliceText("[");

					text = text.replace(/\s+/g, " ");					

					text = text.trim();

					

					if(text == "") {

						nodeList.pop();

						break;

					}



					var attrs = linkAttrStack.pop();

					var url;

					attrs["href"] &&  attrs["href"].value != "" ? url = getNormalizedUrl(attrs["href"].value) : url = "";

					

					if(url == "") {

						nodeList.pop();

						nodeList.push(text);

						break;

					}

					

					nodeList.push(text);

					

					if(!inlineStyle && !peek(nodeList).startsWith("!")){

						var l = links.indexOf(url);

						if(l == -1) {

							links.push(url);

							l=links.length-1;

						}							

						nodeList.push("][" + l + "]");

					} else {

						if(peek(nodeList).startsWith("!")){

							var text = nodeList.pop();

							text = nodeList.pop() + text;

							block();

							nodeList.push(text);

						}

						

						var title = attrs["title"];						

						nodeList.push("](" + url + (title ? " \"" + title.value.trim().replace(/\s+/g, " ") + "\"" : "") + ")");

						

						if(peek(nodeList).startsWith("!")){

							block(true);

						}

					}

					break;					

				case "ul":

				case "ol":

				case "dl":	

					listBlock();

					listTagStack.pop();

					break;

				case "li":

				case "dt":

					var li = getListMarkdownTag();

					if(!removeIfEmptyTag(li)) {

						var text = sliceText(li).trim();

						

						if(text.startsWith("[![")) {

							nodeList.pop();							

							block();

							nodeList.push(text);

							block(true);

						} else {

							nodeList.push(text);

							listBlock();

						}

					}	

					break;

				case "blockquote":

					blockquoteStack.pop();

					break;

				case "pre":

				case "code":

					block(true);

					preStack.pop();	

					break;

				case "span":

					if(peek(nodeList).trim() == "") {

						nodeList.pop();

						if(peek(nodeList) == " ") {

							nodeList.pop();	

						} else {

							nodeList.push(markdownTags[tag]);

						}						

					} else {

						var text = nodeList.pop();

						nodeList.push(text.trim());

						nodeList.push(markdownTags[tag]);													

					}

					break;					

				case "br":

				case "hr":

				case "img":

				case "table":	

				case "tr":

					break;

				}

				

			}

		}, {"nodesToIgnore": ["script", "noscript", "object", "iframe", "frame", "head", "style", "label"]});

		

		if(!inlineStyle) {							

			for ( var i = 0; i < links.length; i++) {

				if(i == 0) {

					var lastItem = nodeList.pop();

					nodeList.push(lastItem.replace(/\s+$/g, ""));

					nodeList.push("\n\n[" + i + "]: " + links[i]);

				} else {

					nodeList.push("\n[" + i + "]: " + links[i]);	

				}

			}

		}

	} catch(e) {

		console.log(e.stack);

		console.trace();

	}

	

	return nodeList.join("");

	

}



function getNormalizedUrl(s) {

	var urlBase = location.href;

	var urlDir  = urlBase.replace(/\/[^\/]*$/, '/');

	var urlPage = urlBase.replace(/#[^\/#]*$/, '');



	var url;

	if(/^[a-zA-Z]([a-zA-Z0-9 -.])*:/.test(s)) {

		// already absolute url

		url = s;

	} else if(/^\x2f/.test(s)) {// %2f --> /

		// url is relative to site

		location.protocol != "" ? url = location.protocol + "//" : url ="";		

		url+= location.hostname;		

		if(location.port != "80") {

			url+=":"+location.port;

		}				

		url += s;

	} else if(/^#/.test(s)) {

		// url is relative to page

		url = urlPage + s;

	} else {

		url = urlDir + s;

	}

	return encodeURI(url);

}



if (typeof exports != "undefined") {

	exports.HTML2Markdown = HTML2Markdown;

}

		

if (typeof exports != "undefined") {

	exports.HTML2MarkDown = HTML2MarkDown;

}



/* add the useful functions to String object*/

if (typeof String.prototype.trim != 'function') {

	String.prototype.trim = function() {

		return replace(/^\s+|\s+$/g,"");

	};	

}



if (typeof String.prototype.isNotEmpty != 'function') {

	String.prototype.isNotEmpty = function() {

		if (/\S/.test(this)) {

		    return true;

		} else {

			return false;

		}		

	};	

}



if (typeof String.prototype.replaceAll != 'function') {

	String.prototype.replaceAll = function(stringToFind,stringToReplace){

	    var temp = this;

	    var index = temp.indexOf(stringToFind);

	        while(index != -1){

	            temp = temp.replace(stringToFind,stringToReplace);

	            index = temp.indexOf(stringToFind);

	        }

	        return temp;

	};	

}



if (typeof String.prototype.startsWith != 'function') {

	String.prototype.startsWith = function(str) {

		return this.indexOf(str) == 0;

	};

}



if (typeof String.prototype.endsWith != 'function') {

	String.prototype.endsWith = function(suffix) {

	    return this.match(suffix+"$") == suffix;

	};	

}



if (typeof Array.prototype.indexOf != 'function') {

	Array.prototype.indexOf = function(obj, fromIndex) {

		if (fromIndex == null) {

			fromIndex = 0;

		} else if (fromIndex < 0) {

			fromIndex = Math.max(0, this.length + fromIndex);

		}

		for ( var i = fromIndex, j = this.length; i < j; i++) {

			if (this[i] === obj)

				return i;

		}

		return -1;

	};

}